﻿#region MIT
/*
Blue.ResourceManager
Part of Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008, 2009 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

namespace Blue.ResourceManager
{
    public delegate Resource CreateResourceDelegate( String resourceName, Stream stream );

    public enum ResourceOpenMode
    {
        Read,
        Write,
        TruncateAndWrite
    }

    public class ResourceRenamedEventArgs : EventArgs
    {
        public ResourceRenamedEventArgs( String oldName, String newName )
        {
            OldName = oldName;
            NewName = newName;
        }

        public String OldName;
        public String NewName;
    }

    public class ResourceCreatedEventArgs : EventArgs
    {
        public ResourceCreatedEventArgs( String name )
        {
            Name = name;
        }

        public String Name;
    }

    public class ResourceDeletedEventArgs : EventArgs
    {
        public ResourceDeletedEventArgs( String name )
        {
            Name = name;
        }

        public String Name;
    }

    /// <summary>
    /// The static ResourceManager.
    /// </summary>
    /// <remarks>
    /// The static ResourceManager has to be initialized with an implementation through
    /// ResourceManager.Initialize()
    /// </remarks>
    public static class ResourceManager
    {
        #region Variables
        static AbstractResourceManager _impl;
        #endregion

        #region Events
        /// <see cref="RenameResource"/>
        /// <seealso cref="RenameResourceDirectory"/>
        public static event EventHandler<ResourceRenamedEventArgs> ResourceRenamed
        {
            add { _impl.ResourceRenamed += value; }
            remove { _impl.ResourceRenamed -= value; }
        }


        /// <see cref="RenameResource"/>
        /// <seealso cref="RenameResourceDirectory"/>
        public static event EventHandler<ResourceCreatedEventArgs> ResourceCreated
        {
            add { _impl.ResourceCreated += value; }
            remove { _impl.ResourceCreated -= value; }
        }


        /// <see cref="DeleteResource"/>
        /// <seealso cref="DeleteResourceDirectory"/>
        public static event EventHandler<ResourceDeletedEventArgs> ResourceDeleted
        {
            add { _impl.ResourceDeleted += value; }
            remove { _impl.ResourceDeleted -= value; }
        }


        /// <seealso cref="RenameResourceDirectory"/>
        public static event EventHandler<ResourceRenamedEventArgs> ResourceDirectoryRenamed
        {
            add { _impl.ResourceDirectoryRenamed += value; }
            remove { _impl.ResourceDirectoryRenamed -= value; }
        }


        /// <see cref="OpenResourceStream"/>
        public static event EventHandler<ResourceCreatedEventArgs> ResourceDirectoryCreated
        {
            add { _impl.ResourceDirectoryCreated += value; }
            remove { _impl.ResourceDirectoryCreated -= value; }
        }


        /// <seealso cref="DeleteResourceDirectory"/>
        public static event EventHandler<ResourceDeletedEventArgs> ResourceDirectoryDeleted
        {
            add { _impl.ResourceDirectoryDeleted += value; }
            remove { _impl.ResourceDirectoryDeleted -= value; }
        }
        #endregion

        #region Properties
        /// <summary>
        /// Queries whether the ResourceManager supports writing operations.
        /// </summary>
        public static bool IsReadOnly
        {
            get { return _impl.IsReadOnly; }
        }

        public static bool IsDisposed
        {
            get { return _impl == null || _impl.IsDisposed; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Initializes the static ResourceManager with the given implementation.
        /// </summary>
        /// <param name="implementation"></param>
        public static void Initialize( AbstractResourceManager implementation )
        {
            Debug.Assert( implementation != null );
            Debug.Assert( _impl == null );

            _impl = implementation;
        }

        /// <summary>
        /// Disposes all loaded Resources and after that disposes the resource manager itself.
        /// </summary>
        public static void Dispose()
        {
            _impl.Dispose();
            _impl = null;
        }

        /// <summary>
        /// Registers a factory delegate for a resource type identified by a 
        /// specific file extension.
        /// </summary>
        /// <example>
        /// <code>
        /// ResourceManager.RegisterResourceFactory( ".ximp", ImageMap.LoadFromXmlStream );
        /// </code>
        /// </example>
        /// <param directoryName="ext">The file extension including the prefixed period</param>
        /// <param directoryName="factoryFunction"></param>
        public static void RegisterResourceFactory( String ext,
            CreateResourceDelegate factoryFunction )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            _impl.RegisterResourceFactory( ext, factoryFunction );
        }

        /// <summary>
        /// Returns a ResourceHandle for a given resourceName. Loads the resource if it is not yet
        /// loaded.
        /// </summary>
        /// <typeparam name="T">The Type of the Resource to be acquired.</typeparam>
        /// <param name="resourceName">The name of the resource to be acquired.</param>
        /// <returns>A handle for the resource.</returns>
        public static T GetResource<T>( String resourceName ) where T : Resource
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.GetResource<T>( resourceName );
        }

        /// <summary>
        /// Returns whether the resource is currently loaded.
        /// </summary>
        /// <param directoryName="directoryName"></param>
        /// <returns></returns>
        public static bool IsResourceLoaded( String name )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.IsResourceLoaded( name );
        }

        /// <summary>
        /// Renames a resource. This will also rename or even move the underlying file.
        /// </summary>
        public static void RenameResource( String oldName, String newName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                throw new ResourceManagerException( 
                    "Renaming is not supported in read-only mode",
                    ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY 
                    );
            }

            _impl.RenameResource( oldName, newName );
        }

        /// <summary>
        /// Opens a stream to the underlying file for a given resource directoryName.
        /// </summary>
        /// <returns>
        /// The Stream to the data. The user must close the stream when finished.
        /// </returns>
        /// <remarks>
        /// 
        /// </remarks>
        public static Stream OpenStream( String resourceName, ResourceOpenMode openMode )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                if ( openMode == ResourceOpenMode.Write ||
                    openMode == ResourceOpenMode.TruncateAndWrite )
                {
                    throw new ResourceManagerException(
                        "Write streams are not supported in read-only mode",
                        ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY
                    );
                }
            }

            return _impl.OpenStream( resourceName, openMode );
        }

        /// <summary>
        /// Normalizes a resource directoryName. 
        /// </summary>
        /// <remarks>
        /// All '\' characters are replaced with '/' characters, leading and trailing whitespaces 
        /// are trimmed and a '\' or '/' character at the beginning is removed if present.
        /// </remarks>
        public static String NormalizeName( String name )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.NormalizeName( name );
        }

        /// <summary>
        /// Finds all resources with a given extension in the given search directory.
        /// </summary>
        /// <param directoryName="searchDir">
        /// The directory that is searched through including all of its sub-directories. Can be 
        /// empty or "/" for searching the root directory.
        /// </param>
        /// <param directoryName="ext">
        /// The resource extension. Can be "*" to match all extensions.
        /// </param>
        /// <returns>
        /// An IEnumerable of Strings containing the paths of all 
        /// resources found RELATIVE to the search directory.
        /// </returns>
        /// <example>
        /// If there are the files "x.png", "y.png", "z.bmp", and the subdirectory "abc", 
        /// containing the file "q.png" in the directory "Images" then the calls
        /// 
        /// /Images
        ///     /x.png
        ///     /y.png
        ///     /z.bmp
        ///     /abc
        ///         /q.png
        /// 
        /// <code>
        /// ResourceManager.FindResources( "Images", ".png" );
        /// ResourceManager.FindResources( "/Images/", ".png" );
        /// ResourceManager.FindResources( "Images/", ".png" );
        /// ResourceManager.FindResources( "/Images", ".png" );
        /// </code>
        /// will all result in the same list filenames:
        /// <code>
        /// { "x.png", "y.png", "abc/q.png" }
        /// </code>
        /// </example>
        public static IEnumerable<String> FindResources( String searchDir, String ext )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.FindResources( searchDir, ext );
        }

        /// <summary>
        /// Finds all sub-directories of the given search directory.
        /// </summary>
        /// <see cref="FindResources"/>
        public static IEnumerable<String> FindDirectories( String searchDir )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.FindDirectories( searchDir );
        }

        /// <summary>
        /// Returns true if the resource exists.
        /// </summary>
        public static bool ResourceExists( String resourceName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.ResourceExists( resourceName );
        }

        /// <summary>
        /// Returns true if the directory exists.
        /// </summary>
        public static bool ResourceDirectoryExists( String directoryName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.ResourceDirectoryExits( directoryName );
        }


        /// <summary>
        /// Use this method to create a new resource.
        /// </summary>
        /// <param name="resourceName">The name of the new resource.</param>
        /// <returns>Returns a ResourceCreationStream object. Must be closed by the user.</returns>
        /// <exception cref="ObjectDisposedException">Thrown when the resource manager is disposed.
        /// </exception>
        /// <exception cref="ResourceManagerException">
        /// Thrown if this is a read-only resource manager
        /// -or-
        /// if there already is a resource with the given name
        /// -or-
        /// if for some reason the resource stream could not be opened for writing.
        /// </exception>
        /// <remarks>
        /// This method allows for creating new resources. The method returns a 
        /// ResourceCreationStream object. The contents of the resource can be written to the 
        /// stream. When finished the stream must be closed by the user. When the stream is closed
        /// the ResourceManager.ResourceCreated event is triggered.
        /// The creation of the resource can be cancelled by setting the ResourceCreationAborted
        /// property of the ResourceCreationStream object to 'true' before closing the stream.
        /// The ResourceCreated event will then not be triggered.
        /// </remarks>
        public static ResourceCreationStream CreateResource( String resourceName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                throw new ResourceManagerException(
                    "Creating resources is not supported in read-only mode",
                    ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY
                    );
            }

            return _impl.CreateResource( resourceName );
        }

        /// <summary>
        /// Creates a directory with the given resourceName. Does nothing if the directory 
        /// already exists.
        /// </summary>
        /// <exception cref="ResourceManagerException">
        /// Throws a ResourceManagerException if the implementation does not support writing.
        /// </exception>
        public static void CreateResourceDirectory( String directoryName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                throw new ResourceManagerException(
                    "Creating resource directories is not supported in read-only mode",
                    ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY
                    );
            }

            _impl.CreateResourceDirectory( directoryName );
        }

        /// <summary>
        /// Renames a resource directory.
        /// </summary>
        /// <remarks>
        /// Raises the ResourceRenamed event for resource contained in the renamed directory.
        /// After that raises the ResourceDirectoryRenamed event.
        /// </remarks>
        /// <exception cref="ResourceManagerException">
        /// Throws a ResourceManagerException of the directory is not found, a directory
        /// with the given new resourceName already exists, or some error specific to the 
        /// ResourceManager implementation occurs (e.g.: No file system permission).
        /// Also throws a ResourceManagerException if the implementation does not 
        /// support writing.
        /// </exception>
        public static void RenameResourceDirectory( String oldName, String newName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                throw new ResourceManagerException(
                    "Renaming resource directories is not supported in read-only mode",
                    ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY
                    );
            }

            _impl.RenameResourceDirectory( oldName, newName );
        }

        /// <summary>
        /// Deletes a resource.
        /// </summary>
        /// <remarks>
        /// Deletes the underlying file and disposes the Resource if it is currently loaded.
        /// The ResourceDeleted event is raised after a resource has been deleted.
        /// </remarks>
        /// <exception cref="ResourceManagerException">
        /// Throws a ResourceManagerException if the implementation does not support writing.
        /// Also throws a ResourceManagerException if some implementation specific error
        /// occurs (e.g.: no filesystem permission).
        /// </exception>
        public static void DeleteResource( String resourceName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                throw new ResourceManagerException(
                    "Deleting resources is not supported in read-only mode",
                    ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY
                    );
            }

            _impl.DeleteResource( resourceName );
        }

        /// <summary>
        /// Deletes a resource directory. 
        /// </summary>
        /// <remarks>
        /// Calls DeleteResource for every resource in the directory.
        /// The ResourceDeleted event is raised for each resource contained in the 
        /// directory. After that the ResourceDirectoryDeleted event is raised.
        /// </remarks>
        public static void DeleteResourceDirectory( String directoryName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            if ( IsReadOnly )
            {
                throw new ResourceManagerException(
                    "Deleting directories is not supported in read-only mode",
                    ResourceManagerException.Reason.NOT_SUPPORTED_FOR_READONLY
                    );
            }

            _impl.DeleteResourceDirectory( directoryName );
        }

        /// <summary>
        /// Determines that the given directory contains neither resources nor
        /// subdirectories.
        /// </summary>
        public static bool IsResourceDirectoryEmpty( String directoryName )
        {
            if ( _impl.IsDisposed )
            {
                throw new ObjectDisposedException( "ResourceManager",
                    "The ResourceManager is disposed." );
            }

            return _impl.IsResourceDirectoryEmpty( directoryName );
        }

        // TODO: This is a bit lame at the moment. Make it better.
        public static bool IsValidResourceName( String resourceName )
        {
            if ( String.IsNullOrEmpty( resourceName ) )
                return false;

            resourceName = NormalizeName( resourceName );
            resourceName = resourceName.Replace( Path.DirectorySeparatorChar, '/' );

            // Check for invalid characters
            if ( resourceName.IndexOfAny( Path.GetInvalidPathChars() ) >= 0 )
                return false;

            // Check for nameless directories
            if ( resourceName.Contains( "//" ) )
                return false;

            // Check if there is an extension
            if ( String.IsNullOrEmpty( Path.GetExtension( resourceName ) ) )
                return false;

            return true;
        }

        /// <summary>
        /// Only for internal use by the Resource class.
        /// </summary>
        internal static void OnResourceDisposed( Resource resource )
        {
            if ( !_impl.IsDisposed )
            {
                _impl.OnResourceDisposed( resource );
            }
        }

        /// <summary>
        /// Only for internal use by the Resource class.
        /// </summary>
        internal static void OnResourceCreationStreamDisposed( 
            ResourceCreationStream stream ) 
        {
            if ( !_impl.IsDisposed )
            {
                _impl.OnResourceCreationStreamDisposed( stream );
            }
        }
        #endregion
    }
}
